package org.greatstorm.dao;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.logging.Logger;

import org.greatstorm.entity.EntityObject;
import org.greatstorm.util.Filter;
import org.greatstorm.util.Sort;
import org.greatstorm.util.Utils;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Text;
import com.google.appengine.api.memcache.MemcacheService;
import com.google.appengine.api.memcache.MemcacheServiceFactory;

public abstract class GeneralDao {

	private static final Logger log = Logger.getLogger(GeneralDao.class
			.getName());

	protected DatastoreService ds;
	protected MemcacheService cache;

	Key parent = KeyFactory.createKey("root", "inspire.liu@gmail.com");

	public GeneralDao() {
		ds = DatastoreServiceFactory.getDatastoreService();
		cache = MemcacheServiceFactory.getMemcacheService(this.getEntityName());
	}

	public GeneralDao(DatastoreService ds) {
		this.ds = ds;
	}

	public int count() {
		return ds.prepare(new Query(getEntityName())).countEntities(
				FetchOptions.Builder.withLimit(100));
	}

	public void save(EntityObject obj) {
		String kind = getEntityName();
		String ret = Utils.getKeyString();
		final Entity entity = new Entity(kind, ret, parent);
		// set the entity key
		obj.setKey(ret);

		Set<String> props = obj.getPropertyNames();

		// set entity property value
		for (String p : props) {
			Object o = obj.get(p);

			// handle the Text
			if (o instanceof String) {
				if (((String) o).length() > 512) {
					Text t = new Text((String) o);
					entity.setProperty(p, t);

				} else {
					entity.setProperty(p, o);
				}
			} else {
				entity.setProperty(p, o);
			}

		}

		log.finest("current timezone is : " + TimeZone.getDefault());

		Date lastmodifiedTime = new Date(System.currentTimeMillis());
		Date createTime = new Date(System.currentTimeMillis());

		entity.setProperty("lastmodified", lastmodifiedTime);
		entity.setProperty("create_date", createTime);

		obj.put("lastmodified", lastmodifiedTime);
		obj.put("create_date", createTime);

		ds.put(entity);

		// put it into level-1 cache
		cache.put(ret, obj);

	}

	public void update(EntityObject obj) {
		String kind = getEntityName();
		String ret = obj.getKey();
		final Entity entity = new Entity(kind, ret, parent);

		Set<String> props = obj.getPropertyNames();
		// set entity property value
		for (String p : props) {
			Object o = obj.get(p);

			// handle the Text
			if (o instanceof String) {
				if (((String) o).length() > 512) {
					Text t = new Text((String) o);
					entity.setProperty(p, t);

				} else {
					entity.setProperty(p, o);
				}
			} else {
				entity.setProperty(p, o);
			}
		}

		Date lastmodifiedTime = new Date(System.currentTimeMillis());
		entity.setProperty("lastmodified", lastmodifiedTime);
		obj.put("lastmodified", lastmodifiedTime);

		ds.put(entity);

		cache.put(ret, obj);

	}

	public void delete(String key) {
		Key delKey = KeyFactory.createKey(parent, getEntityName(), key);
		ds.delete(delKey);

		cache.delete(key);
	}

	protected abstract String getEntityName();

	public EntityObject getEntityByKey(String key) {
		// get from cache
		Object objInCache = cache.get(key);

		if (objInCache != null) {
			log.info("read a object from cache");
			return (EntityObject) objInCache;
		}

		Key qKey = KeyFactory.createKey(parent, getEntityName(), key);

		Entity entity = null;
		try {
			entity = ds.get(qKey);
		} catch (EntityNotFoundException e1) {
			log.warning("no entity found: " + e1);
		}

		EntityObject obj = null;
		if (entity != null) {
			obj = new EntityObject();

			obj.setKind(getEntityName());
			obj.setKey((String) entity.getProperty("key"));

			Map<String, Object> propsMap = entity.getProperties();
			Set<String> props = propsMap.keySet();
			// set entity property value
			for (String p : props) {
				Object o = propsMap.get(p);
				if (o instanceof Text) {
					obj.put(p, ((Text) o).getValue());
				} else {
					obj.put(p, o);
				}

			}
		}

		return obj;
	}

	public List<EntityObject> getEntityListByFilter(Filter filters,
			Sort... sorter) {
		List<Filter> filterList = new ArrayList<Filter>();
		filterList.add(filters);
		return this.getEntityListByFilter(1, Integer.MAX_VALUE, filterList,
				sorter);
	}

	public EntityObject getUniqueEntity(Filter filters, Sort... sorter) {
		List<Filter> filterList = new ArrayList<Filter>();
		filterList.add(filters);

		List<EntityObject> list = this.getEntityListByFilter(1, 25, filterList,
				sorter);
		if (list.size() == 0) {
			return null;
		}

		return list.get(0);
	}

	public List<EntityObject> getEntityListByFilter(int curPageNo,
			int pageSize, List<Filter> filters, Sort... sorter) {
		final Query query = new Query(getEntityName());
		String propName = null;
		Query.FilterOperator filterOperator = null;
		Object propVal = null;

		for (Filter f : filters) {
			propName = f.getKey();
			propVal = f.getValue();

			switch (f.getOpt()) {
			case OPT_EQUAL:
				filterOperator = Query.FilterOperator.EQUAL;
				break;
			case OPT_GT:
				filterOperator = Query.FilterOperator.GREATER_THAN;
				break;
			case OPT_GET:
				filterOperator = Query.FilterOperator.GREATER_THAN_OR_EQUAL;
				break;
			case OPT_LT:
				filterOperator = Query.FilterOperator.LESS_THAN;
				break;
			case OPT_LET:
				filterOperator = Query.FilterOperator.LESS_THAN_OR_EQUAL;
				break;
			case OPT_NOT_EQUAL:
				filterOperator = Query.FilterOperator.NOT_EQUAL;
				break;
			}
			query.addFilter(propName, filterOperator, propVal);
		}

		Query.SortDirection sortDir = null;
		for (Sort s : sorter) {
			propName = s.getSortKey();

			switch (s.getSortDirection()) {
			case ASC:
				sortDir = Query.SortDirection.ASCENDING;
				break;
			case DESC:
				sortDir = Query.SortDirection.DESCENDING;
				break;
			}
			query.addSort(propName, sortDir);
		}

		int offset = (curPageNo - 1) * pageSize;
		List<Entity> ret = ds.prepare(query).asList(
				FetchOptions.Builder.withOffset(offset).limit(pageSize));

		List<EntityObject> list = new ArrayList<EntityObject>(ret.size());

		for (Entity e : ret) {
			EntityObject o = new EntityObject();
			o.setKind(getEntityName());
			o.setKey((String) e.getProperty("key"));

			Map<String, Object> propsMap = e.getProperties();
			Set<String> props = propsMap.keySet();

			// set entity property value
			for (String p : props) {
				Object o1 = propsMap.get(p);

				if (o1 instanceof Text) {
					o.put(p, ((Text) o1).getValue());
				} else {
					o.put(p, o1);
				}
			}

			list.add(o);
		}

		return list;

	}

	public void deleteAll() {
		final Query query = new Query(getEntityName());
		List<Entity> ret = ds.prepare(query).asList(
				FetchOptions.Builder.withLimit(Integer.MAX_VALUE));

		for (Entity o : ret) {
			ds.delete(o.getKey());
			// cache.delete(o.getKey());
		}
	}

	public List<EntityObject> getAll(int pageno, int pagesize) {
		int offset = (pageno - 1) * pagesize;
		final Query query = new Query(getEntityName());
		List<Entity> ret = ds.prepare(query).asList(
				FetchOptions.Builder.withOffset(offset).limit(pagesize));

		List<EntityObject> list = new ArrayList<EntityObject>(ret.size());

		for (Entity e : ret) {
			EntityObject o = new EntityObject();
			o.setKind(getEntityName());
			o.setKey((String) e.getProperty("key"));

			Map<String, Object> propsMap = e.getProperties();
			Set<String> props = propsMap.keySet();

			// set entity property value
			for (String p : props) {
				Object o1 = propsMap.get(p);

				if (o1 instanceof Text) {
					o.put(p, ((Text) o1).getValue());
				} else {
					o.put(p, o1);
				}
			}

			list.add(o);
		}

		return list;
	}
}